Skip to content

perf(codegen): inline append tier for Array-subclass elements receivers (−6.8% / −8.4% wolf-ecs) - #8996

Merged
proggeramlug merged 2 commits into
PerryTS:mainfrom
proggeramlug:perf/elements-inline-push
Aug 29, 2026
Merged

perf(codegen): inline append tier for Array-subclass elements receivers (−6.8% / −8.4% wolf-ecs)#8996
proggeramlug merged 2 commits into
PerryTS:mainfrom
proggeramlug:perf/elements-inline-push

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

What

sub.push(v) on a class X extends Array instance now takes the inline append
tier. The tier's gate splits three ways instead of two:

  • a forwarded receiver keeps the runtime arm, unchanged;
  • an ordinary GC_TYPE_ARRAY receiver goes straight to the room/integrity tests
    and the inline store, exactly as before — byte-identical IR;
  • a GC_TYPE_OBJECT receiver loads its meta record and ObjectMeta.elements
    (word 12) and validates that store — an Array, not forwarded — before the same
    tests and the same inline store run on it, reached through a payload phi.

The store's own header carries the length that is bumped and the flags that are
tested, and it is the write barrier's parent. The binding keeps naming the
receiver, and capacity growth still calls js_array_push_f64_spec with the
receiver so the re-allocated head is published where the runtime expects it.

Why

Since the Array-subclass elements store landed (#8966), the representation of a
subclass instance is a real GC_TYPE_ARRAY hanging off ObjectMeta. But the
inline append tier still declined every non-GC_TYPE_ARRAY receiver — a
deliberate refusal from #7574, because an ObjectHeader overlays ArrayHeader
field for field, so an inline store against an object receiver would have
written over keys_array. That refusal sent every sub.push(v) to
js_array_push_f64 / js_array_push_f64_spec for what is now a single pointer
hop: together 4.2% of the wolf-ecs entity cycle.

This is the append half of the same fix #8976 made for pop.

Numbers

Mac mini (perry@perry-macos.local, load ~1.5), 11 alternating pairs against
main at 4b9dde9, both benchmarks in both windows:

benchmark window main this PR delta wins
wolf-ecs add_remove 50 ms 0.3283 0.3060 −6.78% 11/11
wolf-ecs entity_cycle 50 ms 0.2650 0.2428 −8.34% 11/11
wolf-ecs add_remove 2 s 0.3284 0.3061 −6.80% 11/11
wolf-ecs entity_cycle 2 s 0.2652 0.2429 −8.39% 11/11

Testing

  • RUSTFLAGS=-D warnings cargo check --workspace --all-targets — clean.
  • perry-codegen suite, including all 15 array_push IR tests.
  • perry-runtime --lib.
  • Integration: issue_8655_array_subclass_indexing,
    issue_8773_closure_capture_packed_loops, issue_8690_loop_versioned_arraylike,
    issue_5898_array_pop_prototype, array_subclass_fill_args,
    issue_8897_field_push_writeback.
  • Lint gates: shape-descriptor census, address-class inventory, GC store-site
    inventory, file size, raw-handle debt, local-binding audit.
  • Differential probes against node: subclass-semantics, subclass-heavy,
    fill-args, elem-loop — no divergence introduced (the two pre-existing
    Symbol.species constructor.name differences are unchanged).

Summary by CodeRabbit

  • Performance
    • Improved push() performance for eligible Array subclasses by enabling a faster inline append path.
    • Preserved existing behavior for growing arrays, forwarded receivers, and special array configurations.

…payload

The `apush` tier routed every non-`GC_TYPE_ARRAY` receiver to the runtime
(PerryTS#7574 — an `ObjectHeader` overlays `ArrayHeader` field for field, so the
inline store would have written over `keys_array`). With the elements
store as the representation that sends every `sub.push(v)` to
`js_array_push_f64`/`_spec` — 4.2% of the wolf-ecs entity cycle — for a
single pointer hop.

The gate now splits three ways: a forwarded receiver keeps the runtime
arm; an ordinary Array goes straight to the room/flag tests as before; a
`GC_TYPE_OBJECT` receiver loads its meta record and `ObjectMeta.elements`
(word 12) and validates that store (an Array, not forwarded) before the
same tests and inline store run on it through a payload phi. The store's
own header carries the length that is bumped and the flags that are
tested, and it is the barrier's parent; the binding keeps naming the
receiver, and growth still calls the runtime with the receiver so the
re-allocated head is published there.

Claude-Session: https://claude.ai/code/session_019WVcWKmYsUBnnFB7nBgbBJ
@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8b0c2bfb-5658-427e-b01f-c07e950091a9

📥 Commits

Reviewing files that changed from the base of the PR and between 4b9dde9 and 7af3ec9.

📒 Files selected for processing (2)
  • changelog.d/8996-elements-inline-push.md
  • crates/perry-codegen/src/expr/array_push.rs

📝 Walkthrough

Walkthrough

ArrayPush now inlines appends for valid elements-backed Array subclass receivers. It resolves the backing store, applies existing checks to that store, and preserves the runtime path for growth, forwarding, and exotic flags.

Changes

Inline Array subclass push

Layer / File(s) Summary
Elements store resolution
crates/perry-codegen/src/expr/array_push.rs
The fast path reads the receiver meta slot and validates a live, non-forwarded GC_TYPE_ARRAY store. Valid stores proceed to the inline path; other cases use the runtime path.
Payload-based inline append
crates/perry-codegen/src/expr/array_push.rs, changelog.d/8996-elements-inline-push.md
The inline tiers use the resolved payload for integrity checks, capacity and length loads, element stores, write barriers, write notes, and the length update. The changelog records the optimization.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ArrayPush
  participant ObjectMeta
  participant ArrayStore
  participant InlineStore
  ArrayPush->>ObjectMeta: Read the receiver meta slot
  ObjectMeta-->>ArrayPush: Return the backing ArrayStore pointer
  ArrayPush->>ArrayStore: Validate store type and forwarding state
  ArrayStore-->>ArrayPush: Provide the resolved payload
  ArrayPush->>InlineStore: Check capacity and store the value
  InlineStore-->>ArrayPush: Bump the payload length
Loading
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Merged (batched with #8995 and #8997).

Splitting the tier's gate three ways rather than two is the right shape here — the elements-backed receiver genuinely is a third case, and #8976 showed what happens when it gets folded into an existing kind whose invariants do not hold for it (element 0 read the object's meta word as a double).

Because this touches that same representation, I did not stop at green unit tests. Re-ran the behavioural probe I built after #8976:

nested closure loop over a subclass   perry=10,20,30,     node=10,20,30,
element 0 after a growth realloc      perry=0,1,2,3,4,5,  node=0,1,2,3,4,5,
single-element subclass               perry=7             node=7
mixed types at element 0              perry=x,2           node=x,2
element 0 after pop()                 perry=1,2           node=1,2

All match. The kill switch also still bisects: PERRY_ARRAY_SUBCLASS_ELEMENTS=0 is 2787/0, same as the default.

One fix pushed: the fragment used the 0000 placeholder; renamed to 8996-.

Validation — runtime 2787/0 (both knob states), codegen 1341/0, stdlib 124/0; scripts/run_lint_gates.sh 57 of 58 with the compile tier green — the exception is the pre-existing Actions-expression artifact (#8929). The wolf-ecs numbers are not re-measured here.

@proggeramlug
proggeramlug merged commit cf15fb2 into PerryTS:main Aug 29, 2026
18 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant